#include "stdafx.h"
#include "CircleDivider.h"
#include "SpecialEquationSolver.h"
#include "DivisionAdjuster.h"
#include <vector>
#include <algorithm>

static const float kTooBigDeviation = 100.0f;

typedef std::vector<float> FloatVector;


//-----------------------------------------------------------------------------

void CircleDivider::ResetSegments(const Fourier& inFourier)
{
	MakeRawSegmentation(inFourier);
}

void CircleDivider::MergeSegments(const Fourier& inFourier)
{
	if (mCount < 3)
	{
		return;
	}
	DivisionAdjuster adjuster(inFourier);
	adjuster.ResetWithAngles(mCount, mTransitionAngles);
	mCount = adjuster.ReduceNumberOfSegments();
	adjuster.OutputAngles(mCount, mTransitionAngles);
	adjuster.OutputColors(mCount, mColors);
}

float CircleDivider::Adjust(const Fourier& inFourier, int& outIterations)
{
	int count = mCount;
	if (count <= 1)
	{
		return 0;
	}

	DivisionAdjuster adjuster(inFourier);
	adjuster.ResetWithAngles(mCount, mTransitionAngles);
	adjuster.RawAdjustAllAngles();

	float deviation = kTooBigDeviation;
	float previousScore = adjuster.CalculateScore();
	int workaroundState = 0;
	
	int iterationCount = 0;;
	for (; iterationCount < kLimitForIterations; ++iterationCount)
	{
		if (iterationCount == 20)
		{
			static int count = 0;
			char buffer[100];
			sprintf_s(buffer, "\n%d too many iterations!", count);
			OutputDebugStringA(buffer);
		}

		deviation = kTooBigDeviation;
		int troublingIndex = kInvalidIndex;
		float score = 0;

		adjuster.RawAdjustAnglesWithWrongSignOfSecondDerivative();
		if (adjuster.SolveLinearEquations())
		{
			troublingIndex = adjuster.GetIndexOfAnglesOverlap();
			if (kInvalidIndex == troublingIndex)
			{
				troublingIndex = adjuster.GetIndexOfBiggestX();
				deviation = ::fabs(adjuster.GetX(troublingIndex));
				if (deviation < 0.01f)
				{
					adjuster.ApplyDeltas();
					break;
				}
				FloatVector oldAngles(mCount + 1);
				adjuster.OutputAngles(mCount, &oldAngles[0]);
				adjuster.ApplyDeltas();
				score = adjuster.CalculateScore();
				if (score < previousScore)
				{
					adjuster.ResetWithAngles(mCount, &oldAngles[0]);
				}
			}
		}		

		if (score < previousScore)
		{
			bool isProcessed = false;

			// the magic numbers (0, 1, 2 and above) are limited to this block of code:
			// they control the invocation sequence of adjuster methods

			if (0 == workaroundState && kInvalidIndex != troublingIndex)
			{
				isProcessed = adjuster.RawAdjustAngle(troublingIndex);
				workaroundState = 1;
			}
			if (!isProcessed && 1 >= workaroundState)
			{
				isProcessed = adjuster.RawAdjustAllAngles();
				workaroundState = 2;
			}
			if (!isProcessed)
			{
				isProcessed = adjuster.MakeGradientAscent();
				if (!isProcessed)
				{
					isProcessed = adjuster.RawAdjustAllAngles();
				}
				++workaroundState;
			}
			score = adjuster.CalculateScore();
			if (score < previousScore)
			{
				// really a problem. It should be made everything possible
				// (in adjuster methods) to avoid coming here, the score 
				// should climb up after each iteration...
				iterationCount = kLimitForIterations;
				break;
			}
		}
		previousScore = score;
	}

	adjuster.OutputAngles(mCount, mTransitionAngles);
	adjuster.OutputColors(mCount, mColors);
	
	outIterations = iterationCount;
	return deviation;
}

int CircleDivider::GetNumberOfSegments() const
{
	return mCount;
}

float CircleDivider::GetAngleAtIndex(int inIndex) const
{
	return mTransitionAngles[inIndex];
}

COLORREF CircleDivider::GetColorAtIndex(int inIndex) const
{
	using namespace DirectX;
	XMVECTOR color = XMVectorClamp(mColors[inIndex],
		XMVectorZero(), XMVectorSet(255, 255, 255, 0));
	int r = lround(color.m128_f32[0]);
	int g = lround(color.m128_f32[1]);
	int b = lround(color.m128_f32[2]);
	return RGB(r, g, b);
}


///////////////////////////////////////////////////////////////////////////////


void CircleDivider::MakeRawSegmentation(const Fourier& inFourier)
{
	int numberOfSegments = 0;

	int numberOfSamples = std::max<int>(4, 3 * inFourier.GetDegree());
	// assert(numberOfSamples <= kMaxNumberOfSegments);

	float deltaAngle = float(2 * M_PI / numberOfSamples);
	std::vector<float> values(numberOfSamples);

	for (int i = 0; i < numberOfSamples; ++i)
	{
		values[i] = inFourier.SquareOfDerivativeAtAngle(i * deltaAngle);
	}

	int count = 0;
	float ppF = values[numberOfSamples - 2];
	float pF = values[numberOfSamples - 1];
	
	for (int i = 0; i < numberOfSamples; ++i)
	{
		float F = values[i];
		if (pF >= F && pF > ppF)
		{
			assert(count < kMaxNumberOfSegments);
			if (count >= kMaxNumberOfSegments)
			{
				break;
			}

			float a2 = F + ppF - 2 * pF;
			mTransitionAngles[count++] = (i - 1 - 0.5f * (F - ppF) / a2) * deltaAngle;
		}
		ppF = pF;
		pF = F;
	}
	if (count <= 1)
	{
		mTransitionAngles[0] = 0;
		mColors[0] = inFourier.GetConstantCoefficient();
		count = 1;
	}
	else if (mTransitionAngles[0] < 0)
	{
		// moving the first angle to the back
		float lastAngle = mTransitionAngles[0] + DirectX::XM_2PI;

		// use memmove for overlapping memory regions
		::memmove(&mTransitionAngles[0], &mTransitionAngles[1],
			(count - 1) * sizeof(mTransitionAngles[0]));

		mTransitionAngles[count - 1] = lastAngle;
	}
	mTransitionAngles[count] = mTransitionAngles[0] + float(2 * M_PI);
	mCount = count;
}

